#include "CL_RSS_Quelle.h"
#include<Poco/Net/HTTPClientSession.h>
#include<Poco/Net/HTTPRequest.h>
#include<Poco/Net/HTTPResponse.h>
#include<Poco/Net/NetException.h>
#include<Poco/URI.h>
#include<Poco/DateTimeParser.h>
#include<Poco/Timestamp.h>
#include<Poco/Hash.h>
#include<string>
#include <ios>
#include <iostream>
#include <ctime>
#include <ostream>
#include <istream>
#include <sstream>
#include<unistd.h>
#include "unicode/platform.h"
#include "unicode/std_string.h"
#include "unicode/utypes.h"   /* Basic ICU data types */
//#include "unicode/ucnv.h"     /* C   Converter API    */
#include "unicode/ustring.h"  /* some more string fcns*/
#include "unicode/uchar.h"    /* char names           */
#include "unicode/uloc.h"
#include "unicode/unistr.h"
#include "pugixml.hpp"

const std::string CL_RSS_Quelle::accepted_mime_types="application/rss+xml,application/xml,text/xml";


CL_RSS_Quelle::QError::QError():ErrorDescription("") {};
CL_RSS_Quelle::QError::QError(const std::string& c):ErrorDescription(c) {};



CL_RSS_Quelle::CL_RSS_Quelle(const std::string& u):last_connection(0),last_feed_hash(0), last_news_hash(),
    url(u), is_erroneous(false), last_error("")
{

};
CL_RSS_Quelle::CL_RSS_Quelle():last_connection(0),last_feed_hash(0), last_news_hash(),
    url(""), is_erroneous(false), last_error("")
{

};

CL_RSS_Quelle::operator bool() const
{
    return url==""?false:true;
};

void CL_RSS_Quelle::set_url(const std::string& s)
{
    if(s!=url)
    {
        url=s;
        last_news_hash.clear();
        is_erroneous = false;
        last_error.set_error("");
        last_feed_hash=0;
    };
};
CL_RSS_Quelle& CL_RSS_Quelle::operator=(const CL_RSS_Quelle& q)
{
    if(this!=&q)
    {
        last_connection=q.last_connection;
        last_feed_hash=q.last_feed_hash;
        last_news_hash=q.last_news_hash;
        url=q.url;
        is_erroneous=q.is_erroneous;
        last_error=q.last_error;
    }
    return *this;
}

CL_RSS_Quelle::~CL_RSS_Quelle()
{
}

std::string& CL_RSS_Quelle::get_response_utf8(std::string& body)
{
    body.clear();
    if(is_erroneous) throw last_error;
    Poco::URI uri(url);
    Poco::Net::HTTPClientSession client(uri.getHost(), uri.getPort());
    Poco::Net::HTTPRequest request(Poco::Net::HTTPRequest::HTTP_GET, uri.toString());//uri.getPath()
    request.setHost(uri.getHost());
    request.add("User-Agent","MK2366 Reader");
#warning TODO (markus#1#): gzip or deflate!
//    request.add("Accept-Encoding","gzip");
#warning TODO (markus#1#): application/rdf+xml, and application/atom+xml sollten berücksichtigt werden
        request.add("Accept",accepted_mime_types);
    try
    {
        client.sendRequest(request);
    }
    catch(Poco::Net::HostNotFoundException e)
    {
#warning TODO (markus#1#): Logging fehlt
        is_erroneous=true;
        last_error.set_error("Host not found");
        throw last_error;
    }
    catch(Poco::Net::DNSException e)
    {
#warning TODO (markus#1#): Logging fehlt
        is_erroneous=true;
        last_error.set_error("DNS Error");
        throw last_error;
    }
    Poco::Net::HTTPResponse response;
    std::istream& responsestream = client.receiveResponse(response);
#warning TODO (markus#1#): Nur für Debugging!\

    std::cout<<"##################################################"<<std::endl;
    std::cout<<time(NULL)<<": Response an "<< url <<" geschickt."<<std::endl;
    std::cout<<"##################################################"<<std::endl;
    int status=response.getStatus();
    if (status!=200)
    {
        is_erroneous=true;
        last_error.set_error("HTTP Status code not 200");
        throw last_error;
    }
    const std::string& content_type = response.get("Content-Type");
    std::string encoding_type=std::string("");
    if(response.has("Content-Encoding")) encoding_type=response.get("Content-Encoding");
#warning TODO (markus#1#): clean up string handling by MessageHeader.SplitParameter etc
    std::string scharset = "ISO 8859-1";
    std::string content_type_without_attr = content_type.substr(0,content_type.find(";"));
    if(CL_RSS_Quelle::accepted_mime_types.find(content_type_without_attr)==CL_RSS_Quelle::accepted_mime_types.npos)
    {
        is_erroneous=true;
        last_error.set_error("Content type not accepted.");
        throw last_error;
    }
    size_t icharset=content_type.find("charset=");
    if (icharset!=content_type.npos)
    {
        icharset+=8;
        scharset=content_type.substr(icharset);
        size_t ionlyattrib = scharset.find(";");
        scharset=scharset.substr(0,ionlyattrib);
    };
    char c;
    while (responsestream.get(c))
    {
        body+=c;
    }
    UnicodeString US(body.c_str(),scharset.c_str());
    body.clear();
    US.toUTF8String(body);
    return body;
}

int CL_RSS_Quelle::parse_rss_20(newsmap& map, pugi::xml_document& rss_doc, pugi::xml_node& version)
{
    std::set<std::size_t> news_hashes;
    int zaehler = 0;
    for(pugi::xml_node channel=version.child("channel"); channel; channel=channel.next_sibling("channel"))
    {
        CL_channel struct_channel;
        struct_channel=CL_channel();
        /*
                struct_channel.image.url;
                struct_channel.image.titel;
                struct_channel.image.link;*/
        if(pugi::xml_node tmp_node=channel.child("title"))struct_channel.titel=tmp_node.child_value();
        if(pugi::xml_node tmp_node=channel.child("link"))struct_channel.url=tmp_node.child_value();
        if(pugi::xml_node tmp_node=channel.child("description"))struct_channel.description=tmp_node.child_value();
        if(pugi::xml_node tmp_node=channel.child("language"))struct_channel.language=tmp_node.child_value();
        if(pugi::xml_node tmp_node=channel.child("copyright"))struct_channel.copyright=tmp_node.child_value();
        if(pugi::xml_node tmp_node=channel.child("pubDate"))
        {
            std::string s=tmp_node.child_value();
            Poco::DateTime dateTime;
            int tzd;
            Poco::DateTimeParser::parse(s,dateTime, tzd);
            dateTime.makeUTC(tzd);
            Poco::Timestamp ts=dateTime.timestamp();
            struct_channel.pubDate=ts.epochTime();
        }
        if(pugi::xml_node tmp_node=channel.child("image"))
        {
            if(pugi::xml_node tmp_node2=tmp_node.child("url"))struct_channel.img.url=tmp_node2.child_value();
            if(pugi::xml_node tmp_node2=tmp_node.child("title"))struct_channel.img.titel=tmp_node2.child_value();
            if(pugi::xml_node tmp_node2=tmp_node.child("link"))struct_channel.img.link=tmp_node2.child_value();
        }

        Poco::Hash<std::string> myhash;
        for(pugi::xml_node item=channel.child("item"); item; item=item.next_sibling("item"))
        {
            key_pair kp;
            kp = key_pair();
            CL_newsitem ni;
            ni=CL_newsitem();
            if(pugi::xml_node tmp_node=item.child("guid"))kp.guid=myhash(tmp_node.value());
            if(pugi::xml_node tmp_node=item.child("title"))ni.title=tmp_node.child_value();
            if(pugi::xml_node tmp_node=item.child("description"))ni.description=tmp_node.child_value();
            if(ni.description==""&&ni.title=="")continue; /* no valid news item according to RSS 2.0 spec */
            if(kp.guid==0) kp.guid=ni.description==""?myhash(ni.title):myhash(ni.description);
            news_hashes.insert(kp.guid);
            if(!(last_news_hash.find(kp.guid)==last_news_hash.end()))continue; /*news already got in last parse */
            zaehler++;
            if(pugi::xml_node tmp_node=item.child("pubDate"))
            {
                std::string s=tmp_node.child_value();
                Poco::DateTime dateTime;
                int tzd;
                Poco::DateTimeParser::parse(s,dateTime, tzd);
                dateTime.makeUTC(tzd);
                Poco::Timestamp ts=dateTime.timestamp();
                ni.pubDate=kp.t=ts.epochTime();
            }
            else
            {
                kp.t=struct_channel.pubDate==0?time(NULL):struct_channel.pubDate;
            }
            if(pugi::xml_node tmp_node=item.child("author"))ni.author=tmp_node.child_value();
            if(pugi::xml_node tmp_node=item.child("link"))ni.link=tmp_node.child_value();
            map[kp].channel=struct_channel;
            map[kp].newsitem=ni;
        }
    }
    last_news_hash=news_hashes;
    return zaehler;
}


int CL_RSS_Quelle::create_news_from_xml(std::string& xml_utf8, newsmap& map)
{
    pugi::xml_document rss_doc;
    std::istringstream xml_utf8_stream(xml_utf8);
    pugi::xml_parse_result result = rss_doc.load(xml_utf8_stream,pugi::parse_default,pugi::encoding_utf8);
    pugi::xml_node version=rss_doc.child("rss");
    if (version)
    {
//can be RSS
        pugi::xml_attribute version_att=version.attribute("version");
        if(version_att)
        {
//RSS 2.0?
            if(strcmp(version_att.value(),"2.0"))
            {
                last_error.set_error("XML Paring: couldn't find rss version");
                throw last_error;
            }
//okay: RSS 2.0 steam found
            return parse_rss_20(map, rss_doc, version);
        }
        else
        {
            last_error.set_error("XML Paring: couldn't find rss version");
            throw last_error;
        }
    }
    else
    {
//cannot be RSS maybe atom?
#warning TODO (markus#1#): implement atom parsing
        last_error.set_error("XML Paring: couldn't find rss version");
        throw last_error;
    }
}

newsmap& operator+(newsmap& m, CL_RSS_Quelle& q)
{
    if (q.is_erroneous||(time(NULL)-q.last_connection)<120 )return m;
    q.last_connection=time(NULL);
    std::string s="";
    Poco::Hash<std::string> myhash;
    size_t hash=myhash(q.get_response_utf8(s));
    if(hash==q.last_feed_hash) return m;
    q.last_feed_hash=hash;
    q.create_news_from_xml(s, m);
    return m;
}

newsmap& operator+=(newsmap& m, CL_RSS_Quelle& q)
{
    return m=m+q;
}






